﻿using System.Collections.Generic;
using System.Linq;
using MugenInjection.Infrastructure;

// ReSharper disable CheckNamespace

namespace System.Reflection
// ReSharper restore CheckNamespace
{
    /// <summary>
    /// Marks each type of member that is defined as a derived class of MemberInfo.
    /// </summary>
    [Flags]
    public enum MemberTypes
    {
        Constructor = 1,
        Event = 2,
        Field = 4,
        Method = 8,
        Property = 16,
        TypeInfo = 32,
        Custom = 64
    }

    public static class ReflectionExtensions
    {
        #region Fields

        private static readonly CacheCollection<Type, MethodInfo[]> Methods = new CacheCollection<Type, MethodInfo[]>(30);

        private static readonly CacheCollection<Type, PropertyInfo[]> Properties =
            new CacheCollection<Type, PropertyInfo[]>(30);

        private static readonly CacheCollection<Type, EventInfo[]> Events = new CacheCollection<Type, EventInfo[]>(30);
        private static readonly CacheCollection<Type, FieldInfo[]> Fields = new CacheCollection<Type, FieldInfo[]>(30);

        #endregion

        #region Methods

        /// <summary>
        /// Gets a <see cref="T:System.Reflection.MemberTypes"/> value.
        /// </summary>
        /// <param name="memberInfo"></param>
        /// <returns></returns>
        public static MemberTypes GetMemberType(this MemberInfo memberInfo)
        {
            if (memberInfo is ConstructorInfo)
                return MemberTypes.Constructor;
            if (memberInfo is EventInfo)
                return MemberTypes.Event;
            if (memberInfo is FieldInfo)
                return MemberTypes.Field;
            if (memberInfo is MethodInfo)
                return MemberTypes.Method;
            if (memberInfo is PropertyInfo)
                return MemberTypes.Property;
            if (memberInfo is TypeInfo)
                return MemberTypes.TypeInfo;
            return MemberTypes.Custom;
        }

        /// <summary>
        /// Returns all the methods of the current <see cref="T:System.Type"/>.
        /// </summary> 
        /// <returns>
        /// An array of <see cref="T:System.Reflection.MethodInfo"/> objects representing all methods defined for the current <see cref="T:System.Type"/>.
        /// </returns>
        public static MethodInfo[] GetMethods(this Type type)
        {
            lock (Methods)
            {
                MethodInfo[] methodInfos = Methods[type];
                if (methodInfos == null)
                {
                    methodInfos = type.GetMethodsInternal().ToArray();
                    Methods[type] = methodInfos;
                }
                return methodInfos;
            }
        }

        /// <summary>
        /// Searches for the method with the specified name.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="name">The <see cref="T:System.String"/> containing the name of the method to get. </param><exception cref="T:System.Reflection.AmbiguousMatchException">More than one method is found with the specified name. </exception><exception cref="T:System.ArgumentNullException"><paramref name="name"/> is null. </exception>
        /// <returns></returns>
        public static MethodInfo GetMethod(this Type type, string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            IEnumerable<MethodInfo> memberInfos = type.GetMethods().Where(info => info.Name.Equals(name));
            int count = memberInfos.Count();
            if (count == 0)
                return null;
            if (count != 1)
                throw new AmbiguousMatchException("More than one method is found with the specified name.");
            return memberInfos.First();
        }

        /// <summary>
        /// Searches for the specified public method whose parameters match the specified argument types.
        /// </summary>
        /// 
        /// <returns>
        /// A <see cref="T:System.Reflection.MethodInfo"/> object representing the public method whose parameters match the specified argument types, if found; otherwise, null.
        /// </returns>
        /// <param name="type"> </param>
        /// <param name="name">The <see cref="T:System.String"/> containing the name of the public method to get. </param><param name="types">An array of <see cref="T:System.Type"/> objects representing the number, order, and type of the parameters for the method to get.</param>
        /// <param name="onlyPublic"></param>
        /// <exception cref="T:System.Reflection.AmbiguousMatchException">More than one method is found with the specified name and specified parameters. </exception><exception cref="T:System.ArgumentNullException"><paramref name="name"/> is null.-or- <paramref name="types"/> is null.-or- One of the elements in <paramref name="types"/> is null. </exception><exception cref="T:System.ArgumentException"><paramref name="types"/> is multidimensional. </exception>
        public static MethodInfo GetMethod(this Type type, string name, Type[] types, bool onlyPublic = false)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            if (types == null)
                throw new ArgumentNullException("types");
            MethodInfo result = null;
            foreach (MethodInfo declaredMethod in type.GetMethods().Where(info => info.Name.Equals(name)))
            {
                if (onlyPublic && !declaredMethod.IsPublic) continue;
                ParameterInfo[] parameterInfos = declaredMethod.GetParameters();
                if (parameterInfos.Length != types.Length) continue;
                bool find = true;
                for (int index = 0; index < parameterInfos.Length; index++)
                {
                    if (parameterInfos[index].ParameterType != types[index])
                    {
                        find = false;
                        break;
                    }
                }
                if (!find) continue;
                if (result != null)
                    throw new AmbiguousMatchException(
                        "More than one method is found with the specified name and specified parameters.");
                result = declaredMethod;
            }
            return result;
        }

        /// <summary>
        /// Returns all the properties of the current <see cref="T:System.Type"/>.
        /// </summary> 
        /// <returns>
        /// An array of <see cref="T:System.Reflection.PropertyInfo"/> objects representing all properties defined for the current <see cref="T:System.Type"/>.
        /// </returns>
        public static PropertyInfo[] GetProperties(this Type type)
        {
            lock (Properties)
            {
                PropertyInfo[] properties = Properties[type];
                if (properties == null)
                {
                    properties = type.GetPropertiesInternal().ToArray();
                    Properties[type] = properties;
                }
                return properties;
            }
        }

        /// <summary>
        /// Searches for the property with the specified name.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="name">The <see cref="T:System.String"/> containing the name of the property to get. </param><exception cref="T:System.Reflection.AmbiguousMatchException">More than one property is found with the specified name. </exception><exception cref="T:System.ArgumentNullException"><paramref name="name"/> is null. </exception>
        /// <returns></returns>
        public static PropertyInfo GetProperty(this Type type, string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            IEnumerable<PropertyInfo> propertyInfos = type.GetProperties().Where(info => info.Name.Equals(name));
            int count = propertyInfos.Count();
            if (count == 0)
                return null;
            if (count != 1)
                throw new AmbiguousMatchException("More than one property is found with the specified name.");
            return propertyInfos.First();
        }

        /// <summary>
        /// Returns all the events of the current <see cref="T:System.Type"/>.
        /// </summary> 
        /// <returns>
        /// An array of <see cref="T:System.Reflection.PropertyInfo"/> objects representing all properties defined for the current <see cref="T:System.Type"/>.
        /// </returns>
        public static EventInfo[] GetEvents(this Type type)
        {
            lock (Events)
            {
                EventInfo[] eventInfos = Events[type];
                if (eventInfos == null)
                {
                    eventInfos = type.GetEventsInternal().ToArray();
                    Events[type] = eventInfos;
                }
                return eventInfos;
            }
        }

        /// <summary>
        /// Searches for the event with the specified name.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="name">The <see cref="T:System.String"/> containing the name of the event to get. </param><exception cref="T:System.Reflection.AmbiguousMatchException">More than one event is found with the specified name. </exception><exception cref="T:System.ArgumentNullException"><paramref name="name"/> is null. </exception>
        /// <returns></returns>
        public static EventInfo GetEvent(this Type type, string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            IEnumerable<EventInfo> eventInfos = type.GetEvents().Where(info => info.Name.Equals(name));
            int count = eventInfos.Count();
            if (count == 0)
                return null;
            if (count != 1)
                throw new AmbiguousMatchException("More than one event is found with the specified name.");
            return eventInfos.First();
        }

        /// <summary>
        /// Returns all the fields of the current <see cref="T:System.Type"/>.
        /// </summary> 
        /// <returns>
        /// An array of <see cref="T:System.Reflection.PropertyInfo"/> objects representing all properties defined for the current <see cref="T:System.Type"/>.
        /// </returns>
        public static FieldInfo[] GetFields(this Type type)
        {
            lock (Fields)
            {
                FieldInfo[] fieldInfos = Fields[type];
                if (fieldInfos == null)
                {
                    fieldInfos = type.GetFieldsInternal().ToArray();
                    Fields[type] = fieldInfos;
                }
                return fieldInfos;
            }
        }

        /// <summary>
        /// Searches for the field with the specified name.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="name">The <see cref="T:System.String"/> containing the name of the field to get. </param><exception cref="T:System.Reflection.AmbiguousMatchException">More than one field is found with the specified name. </exception><exception cref="T:System.ArgumentNullException"><paramref name="name"/> is null. </exception>
        /// <returns></returns>
        public static FieldInfo GetField(this Type type, string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            IEnumerable<FieldInfo> eventInfos = type.GetFields().Where(info => info.Name.Equals(name));
            int count = eventInfos.Count();
            if (count == 0)
                return null;
            if (count != 1)
                throw new AmbiguousMatchException("More than one field is found with the specified name.");
            return eventInfos.First();
        }

        /// <summary>
        /// Indicates whether one or more attributes of the specified type or of its derived types is applied to this member.
        /// </summary>
        /// <param name="type">The specified <see cref="Type"/>.</param>
        /// <param name="attributeType">The type of custom attribute to search for. The search includes derived types. </param><param name="inherit">true to search this member's inheritance chain to find the attributes; otherwise, false. This parameter is ignored for properties and events; see Remarks.</param>
        /// <returns>true if one or more instances of <paramref name="attributeType"/> or any of its derived types is applied to this member; otherwise, false.</returns>
        public static bool IsDefined(this Type type, Type attributeType, bool inherit)
        {
            return type.GetTypeInfo().IsDefined(attributeType, inherit);
        }

        /// <summary>
        /// Gets the types defined in this assembly.
        /// </summary>
        /// <param name="assembly">The specified <see cref="Assembly"/>.</param>
        /// <returns>An array of type <see cref="T:System.Type"/> containing objects for all the types defined in this assembly.</returns>
        public static IEnumerable<Type> GetTypes(this Assembly assembly)
        {
            return assembly.DefinedTypes.Select(info => info.AsType());
        }

        /// <summary>
        /// Searches for a instance constructor whose parameters match the types in the specified array.
        /// </summary>
        /// <param name="type">The specified <see cref="Type"/>.</param>
        /// <param name="types">An array of <see cref="T:System.Type"/> objects representing the number, order, and type of the parameters for the desired constructor.</param>
        /// <returns>A <see cref="T:System.Reflection.ConstructorInfo"/> object representing the public instance constructor whose parameters match the types in the parameter type array, if found; otherwise, null.</returns>
        internal static ConstructorInfo GetConstructor(this Type type, Type[] types)
        {
            foreach (ConstructorInfo constructor in type.GetTypeInfo().DeclaredConstructors)
            {
                ParameterInfo[] constParams = constructor.GetParameters();
                if (types.Length != constParams.Length) continue;
                bool find = true;
                for (int i = 0; i < constParams.Length; i++)
                {
                    if (constParams[i].ParameterType != types[i])
                    {
                        find = false;
                        break;
                    }
                }
                if (!find) continue;
                return constructor;
            }
            return null;
        }

        /// <summary>
        /// Returns an array of <see cref="T:System.Type"/> objects that represent the type arguments of a generic type or the type parameters of a generic type definition.
        /// </summary>
        /// <param name="typeInfo">The specified <see cref="Type"/></param>
        /// <returns>An array of <see cref="T:System.Type"/> objects that represent the type arguments of a generic type. Returns an empty array if the current type is not a generic type.</returns>
        public static Type[] GetGenericArguments(this Type typeInfo)
        {
            return typeInfo.GenericTypeArguments;
        }

        /// <summary>
        /// Determines whether the specified object is an instance of the current <see cref="T:System.Type"/>.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="instance"></param>
        /// <returns></returns>
        public static bool IsInstanceOfType(this Type type, object instance)
        {
            if (instance == null)
                return false;
            return type.IsAssignableFrom(instance.GetType());
        }

        /// <summary>
        /// Determines whether an instance of the current <see cref="T:System.Type"/> can be assigned from an instance of the specified Type.
        /// </summary>
        /// <param name="type">The specified <see cref="Type"/>.</param>
        /// <param name="c">The Type to compare with the current Type.</param>
        /// <returns>true if <paramref name="c"/> and the current Type represent the same type, or if the current Type is in the inheritance hierarchy of <paramref name="c"/>, or if the current Type is an interface that <paramref name="c"/> implements, or if <paramref name="c"/> is a generic type parameter and the current Type represents one of the constraints of <paramref name="c"/>. false if none of these conditions are true, or if <paramref name="c"/> is null.</returns>
        public static bool IsAssignableFrom(this Type type, Type c)
        {
            if (c == null)
                return false;
            return type.GetTypeInfo().IsAssignableFrom(c.GetTypeInfo());
        }

#if DEBUG
        internal static ConstructorInfo[] GetConstructors(this Type type)
        {
            return type.GetTypeInfo().DeclaredConstructors.ToArray();
        }
#endif

        #endregion

        #region Internal methods

        /// <summary>
        /// Returns all the methods of the current <see cref="T:System.Type"/>.
        /// </summary> 
        /// <returns>
        /// An array of <see cref="T:System.Reflection.MethodInfo"/> objects representing all methods defined for the current <see cref="T:System.Type"/>.
        /// </returns>
        private static IEnumerable<MethodInfo> GetMethodsInternal(this Type type)
        {
            if (type == null)
                return new MethodInfo[0];
            var result = new List<MethodInfo>(type.GetTypeInfo().DeclaredMethods);
            foreach (var methodInfo in GetMethodsInternal(type.GetTypeInfo().BaseType))
            {
                bool canAdd = true;
                foreach (var info in result)
                {
                    var isMethodEquals = IsMethodEquals(info, methodInfo);
                    if (!isMethodEquals) continue;
                    canAdd = false;
                    break;
                }
                if (!canAdd) continue;
                result.Add(methodInfo);
            }
            return result;
        }

        /// <summary>
        /// Returns all the property of the current <see cref="T:System.Type"/>.
        /// </summary> 
        /// <returns>
        /// An array of <see cref="T:System.Reflection.PropertyInfo"/> objects representing all methods defined for the current <see cref="T:System.Type"/>.
        /// </returns>
        private static IEnumerable<PropertyInfo> GetPropertiesInternal(this Type type)
        {
            if (type == null)
                return new PropertyInfo[0];
            var result = new List<PropertyInfo>(type.GetTypeInfo().DeclaredProperties);
            foreach (var methodInfo in GetPropertiesInternal(type.GetTypeInfo().BaseType))
            {
                bool canAdd = true;
                foreach (var info in result)
                {
                    if (!info.Name.Equals(methodInfo.Name) || info.PropertyType != methodInfo.PropertyType) continue;
                    if (!info.Name.Equals("Item"))
                    {
                        canAdd = false;
                        break;
                    }
                    if (info.GetMethod != null && methodInfo.GetMethod != null)
                    {
                        if (IsMethodEquals(info.GetMethod, methodInfo.GetMethod))
                        {
                            canAdd = false;
                            break;
                        }
                    }
                    if (info.SetMethod != null && methodInfo.SetMethod != null)
                    {
                        if (IsMethodEquals(info.SetMethod, methodInfo.SetMethod))
                        {
                            canAdd = false;
                            break;
                        }
                    }
                }
                if (!canAdd) continue;
                result.Add(methodInfo);
            }
            return result;
        }

        /// <summary>
        /// Returns all the events of the current <see cref="T:System.Type"/>.
        /// </summary> 
        /// <returns>
        /// An array of <see cref="T:System.Reflection.EventInfo"/> objects representing all methods defined for the current <see cref="T:System.Type"/>.
        /// </returns>
        private static IEnumerable<EventInfo> GetEventsInternal(this Type type)
        {
            if (type == null)
                return new EventInfo[0];
            var result = new List<EventInfo>(type.GetTypeInfo().DeclaredEvents);
            foreach (var methodInfo in GetEventsInternal(type.GetTypeInfo().BaseType))
            {
                bool canAdd = true;
                foreach (var info in result)
                {
                    if (!info.Name.Equals(methodInfo.Name) || info.EventHandlerType != methodInfo.EventHandlerType) continue;
                    canAdd = false;
                    break;
                }
                if (!canAdd) continue;
                result.Add(methodInfo);
            }
            return result;
        }

        /// <summary>
        /// Returns all the fields of the current <see cref="T:System.Type"/>.
        /// </summary> 
        /// <returns>
        /// An array of <see cref="T:System.Reflection.FieldInfo"/> objects representing all methods defined for the current <see cref="T:System.Type"/>.
        /// </returns>
        private static IEnumerable<FieldInfo> GetFieldsInternal(this Type type)
        {
            if (type == null)
                yield break;
            foreach (FieldInfo fieldInfo in type.GetTypeInfo().DeclaredFields)
                yield return fieldInfo;
            foreach (FieldInfo fieldInfo in GetFieldsInternal(type.GetTypeInfo().BaseType))
                yield return fieldInfo;
        }

        internal static MethodInfo GetSetMethod(this PropertyInfo propertyInfo, bool flag)
        {
            return propertyInfo.SetMethod;
        }

        internal static MethodInfo GetGetMethod(this PropertyInfo propertyInfo, bool flag)
        {
            return propertyInfo.GetMethod;
        }

        internal static MethodInfo GetAddMethod(this EventInfo propertyInfo, bool flag)
        {
            return propertyInfo.AddMethod;
        }

        internal static MethodInfo GetRemoveMethod(this EventInfo propertyInfo, bool flag)
        {
            return propertyInfo.RemoveMethod;
        }

        private static bool IsMethodEquals(MethodInfo methodInfo, MethodInfo info)
        {
            if (!info.Name.Equals(methodInfo.Name) || info.ReturnType != methodInfo.ReturnType) return false;
            var parameterInfos = methodInfo.GetParameters();
            var parameters = info.GetParameters();
            if (parameterInfos.Length != parameters.Length) return false;
            bool parameterEqual = true;
            for (int i = 0; i < parameterInfos.Length; i++)
            {
                if (parameterInfos[i].ParameterType != parameters[i].ParameterType)
                {
                    parameterEqual = false;
                    break;
                }
            }
            return parameterEqual;
        }

        #endregion
    }
}